package elephant.servers.webserver;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import javax.servlet.ServletContext;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import elephant.Container;
import elephant.core.ContainerThreadFactory;
import elephant.servers.webserver.core.Controller;
import elephant.servers.webserver.core.DefaultHandler;
import elephant.servers.webserver.core.WebService;
import elephant.servers.webserver.log.JettyLogger;
import elephant.servers.webserver.log.WebRequestLog;
import elephant.servers.webserver.servlet.DispatchServlet;
import elephant.util.FileUtil;
import elephant.util.ObjectPrinter;

/**
 * 
 * @author icecooly
 *
 */
public class WebServer implements elephant.Server{
	//
	private static Logger logger=LoggerFactory.getLogger(WebServer.class);
	//
	public static final String VERSION;
	public static final String SERVER_NAME;
	static{
		VERSION=WebServer.class.getPackage().getImplementationVersion();
		SERVER_NAME="elephant-server-"+VERSION;
	}
	//
	private Server server;
	private HandlerCollection handlers;
	private WebAppContext webAppContext;
	private ContextHandlerCollection contextHandler;
	private RequestLogHandler requestLogHandler;
	private int port=7001;
	private int idleTimeout=30;//30seconds
	private int poolSize=32;
	private boolean disableJettyLogger=true;
	public WebServer() {
	}
	//
	@Override
	public void init()throws Exception{
		if(logger.isInfoEnabled()){
			logger.info(getClass().getSimpleName()+" init");
		}
		final ContainerThreadFactory threadFactory=new ContainerThreadFactory("WebWorker");
		QueuedThreadPool threadPool=new QueuedThreadPool(poolSize){
			@Override
			protected Thread newThread(Runnable runnable) {
				return threadFactory.newThread(runnable);
			}
		};
		server = new Server(threadPool);
		// Add loggers MBean to server (will be picked up by MBeanContainer above)
		server.addBean(Log.getLog());
		handlers = new HandlerCollection();
	    contextHandler=new ContextHandlerCollection();
		requestLogHandler=new RequestLogHandler();
		requestLogHandler.setRequestLog(new WebRequestLog());
	    handlers.setHandlers(new Handler[] {
	    		new DefaultHandler(),
	    		contextHandler,
	    		requestLogHandler});
	}
	//
	@Override
	public void start() throws Exception{
		if(logger.isInfoEnabled()){
			logger.info(getClass().getSimpleName()+" start");
		}
		if(webAppContext!=null){
			contextHandler.addHandler(webAppContext);
		}
		//
		HttpConfiguration httpConfig = new HttpConfiguration();
        ServerConnector httpConnector = new ServerConnector(server,
        		new HttpConnectionFactory(httpConfig)); 
		httpConnector.setPort(port);
		httpConnector.setIdleTimeout(idleTimeout*1000);
		//
		server.setConnectors(new Connector[] {httpConnector});		
        server.setHandler(handlers);
        server.start();
        //
        if(webAppContext!=null){
			Container.get().setAppClassLoader(webAppContext.getClassLoader());
			logger.info("setAppClassLoader "+webAppContext.getClassLoader());
		}
	}
	//
	@Override
	public void stop() throws Exception{
		if(logger.isInfoEnabled()){
			logger.info(getClass().getSimpleName()+" stop");
		}
		server.stop();
	}
	//
	public void addWar(String contextPath,String war) throws Exception{
		webAppContext=createWebAppContext(contextPath);
		webAppContext.setWar(war);
	}
	//
	public void addResource(String contextPath,String resourceBase){
		webAppContext=createWebAppContext(contextPath);
		webAppContext.setResourceBase(resourceBase);
	}
	//
	private WebAppContext createWebAppContext(String contextPath){
		if(disableJettyLogger){
			System.setProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.StdErrLog");
			System.setProperty("org.eclipse.jetty.LEVEL", "OFF");
		}
		WebAppContext webAppContext=new WebAppContext();
		webAppContext.setContextPath(contextPath);
	    webAppContext.setLogger(new JettyLogger());
	    File webApp=new File("webapp");
	    if(!webApp.exists()){
	    	webApp.mkdirs();
	    }
		File tempDir=new File("webapp"+File.separator+Container.get().getServerName());
		FileUtil.deleteDirectory(tempDir);
		if(!tempDir.mkdir()){
			logger.warn("can not create temp dir:"+tempDir.getAbsolutePath());
		}else{
			logger.info("set temp dir to:"+tempDir.getAbsolutePath());
			webAppContext.setTempDirectory(tempDir);		
		}
		List<String>configList=new ArrayList<String>();
		configList.add("org.eclipse.jetty.webapp.WebInfConfiguration");
		configList.add("org.eclipse.jetty.webapp.WebXmlConfiguration");
		configList.add("org.eclipse.jetty.webapp.MetaInfConfiguration");
		configList.add("org.eclipse.jetty.webapp.FragmentConfiguration");
		configList.add("org.eclipse.jetty.plus.webapp.EnvConfiguration");
		configList.add("org.eclipse.jetty.plus.webapp.PlusConfiguration");
		configList.add("org.eclipse.jetty.annotations.AnnotationConfiguration");
		webAppContext.setConfigurationClasses(configList);
		//
		return webAppContext;
	}
	//
	public int getPort() {
		return port;
	}
	public void setPort(int port) {
		this.port = port;
	}
	public int getIdleTimeout() {
		return idleTimeout;
	}
	public void setIdleTimeout(int idleTimeout) {
		this.idleTimeout = idleTimeout;
	}
	//
	public WebAppContext getWebAppContext() {
		return webAppContext;
	}
	//
	public String getRealPath(String path){
		return webAppContext.getServletContext().getRealPath(path);
	}
	//
	private String getImplementationVersion(){
		ServletContext application =webAppContext.getServletContext();
		InputStream manifestStream = application.getResourceAsStream("/META-INF/MANIFEST.MF");
		String impVersion="";
		try {
			Manifest manifest = new Manifest(manifestStream);
			Attributes attributes = manifest.getMainAttributes();
			impVersion = attributes.getValue("Implementation-Version");
			}
		catch(Exception e) {
			logger.error(e.getMessage(),e);
		}
		return impVersion;
	}
	//
	public void enableJettyLogger(){
		disableJettyLogger=false;
	}
	//-------------------------------------------------------------------------
	@Override
	public String dump() {
		ObjectPrinter op=new ObjectPrinter();
		op.section(this.getClass().getSimpleName());
		op.print("version",getImplementationVersion());
		op.print("port",port);
		op.print("idleTimeout",idleTimeout);
		op.print("webAppContext",webAppContext);
		op.section("servlets:");
		if(webAppContext!=null){
			op.println(webAppContext.dump());
			webAppContext.getServletContext().getServletRegistrations().forEach((k,v)->{
				op.println(k);
			});	
			op.section("welcome files");
			int index=0;
			if(webAppContext.getWelcomeFiles()!=null){
				for(String s:webAppContext.getWelcomeFiles()){
					op.print("welcome file-"+(index++),s);						
				}	
			}
			index=0;
			if(webAppContext.getVirtualHosts()!=null){
				for(String s:webAppContext.getVirtualHosts()){
					op.print("virtualHosts"+(index++),s);						
				}	
			}
		}
		//
		List<Controller>csList=DispatchServlet.dispatcher.controllerStubs();
		List<WebService>msList=new ArrayList<WebService>();
		csList.forEach(cs->msList.addAll(cs.methodStubs()));
		Collections.sort(msList);
		op.section("services");
		msList.forEach(op::println);
		return op.toString();
	}
}
